package com.xiaoma.springbootannotation.aspect;

import com.xiaoma.springbootannotation.annotation.CheckCommit;
import com.xiaoma.springbootannotation.annotation.RedisAndLua;
import com.xiaoma.springbootannotation.annotation.SystemLogger;
import com.xiaoma.springbootannotation.entity.SysLog;
import com.xiaoma.springbootannotation.service.SysLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author: xiaoma
 * @Date: 2021/8/9 20:30
 * @description: 注解切面
 */
@Component
@Aspect
@Slf4j
public class AnnotationAspect {

    @Autowired
    private SysLogService sysLogService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * @Description 系统日志切点
     * @Author Xiaoma
     * @Date 2021/8/13 13:40
     * @param
     * @return void
     **/
    @Pointcut("@annotation(com.xiaoma.springbootannotation.annotation.SystemLogger)")
    public void systemLog() {

    }

   /**
    * @Description 接口幂等性切点
    * @Author Xiaoma
    * @Date 2021/8/13 13:39
    * @param
    * @return void
    **/
    @Pointcut("@annotation(com.xiaoma.springbootannotation.annotation.CheckCommit)")
    public void checkCommit() {

    }

   /**
    * @Description Redisson分布式锁切点
    * @Author Xiaoma
    * @Date 2021/8/13 13:39
    * @param
    * @return void
    **/
    @Pointcut("@annotation(com.xiaoma.springbootannotation.annotation.Redisson)")
    public void redisson() {

    }

   /**
    * @Description Redis+Lua脚本分布式锁切点
    * @Author Xiaoma
    * @Date 2021/8/13 13:39
    * @param
    * @return void
    **/
    @Pointcut("@annotation(com.xiaoma.springbootannotation.annotation.RedisAndLua)")
    public void redisAndLua() {

    }
    /**
     * @Description 使用Redis+Lua脚本实现分布式锁
     * @Author Xiaoma
     * @Date 2021/8/13 13:38
     * @param pjp
     * @return java.lang.Object
     **/
    @Around("redisAndLua()")
    public Object doRedisAndLua(ProceedingJoinPoint pjp) {
        Object obj = null;
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();//获取方法
        //判断当前方法是否被RequestMapping注解了 只有注解 才能获取的 方法
        if (method.isAnnotationPresent(RequestMapping.class)) {
            RedisAndLua redisAndLua = method.getAnnotation(RedisAndLua.class);
            //获得注解上的过期时间
            long time = redisAndLua.expireTime();
            Object[] arg = pjp.getArgs();
            //获取方法参数
            String userName = arg[0].toString();
            //原子加锁并设置过期时间
            ValueOperations<String, String> ops = redisTemplate.opsForValue();
            Boolean lock = ops.setIfAbsent("lock", userName, time, TimeUnit.MILLISECONDS);
            String lockValue = ops.get("lock");
            try {
                if (lock) {
                    //获取锁成功，执行业务逻辑
                    Object[] args = pjp.getArgs();
                    obj = pjp.proceed(args);
                } else {
                    //自旋 1S请求1次
                    try {
                        Thread.sleep(1000);
                        log.info("线程" + Thread.currentThread().getName() + "==========正在等待秒杀============");
                        doRedisAndLua(pjp);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            } finally {
                // Lua脚本原子删锁
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                redisTemplate.execute(
                        new DefaultRedisScript<Long>(script, Long.class), // 脚本和返回类型
                        Arrays.asList("lock"), // 参数
                        lockValue); // 参数值，锁的值
            }
        }
        return obj;
    }

    /**
     * @Description 使用Redisson实现分布式锁
     * @Author Xiaoma
     * @Date 2021/8/13 13:38
     * @param pjp
     * @return java.lang.Object
     **/
    @Around("redisson()")
    public Object doRedisson(ProceedingJoinPoint pjp) {
        log.info("线程" + Thread.currentThread().getName() + "==========RedissonAOP方法============");
        Object obj = null;
        RLock lock = redissonClient.getLock("Lock");
        lock.lock();
        try {
            Object[] args = pjp.getArgs();
            obj = pjp.proceed(args);
        } catch (Throwable throwable) {
            lock.unlock();
            throwable.printStackTrace();
        } finally {
            //如果锁是锁着的 && 锁是被当前线程持有
            if (lock.isLocked() && lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
        return obj;
    }

    /**
     * @Description 使用redis setnx命令实现同用户名短时间内不能重复提交
     * @Author Xiaoma
     * @Date 2021/8/13 13:39
     * @param pjp
     * @return java.lang.Object
     **/
    @Around("checkCommit()")
    public Object doCheckCommit(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
//            RequestAttributes ra = RequestContextHolder.getRequestAttributes();
//            ServletRequestAttributes sra = (ServletRequestAttributes)ra;
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            Method method = methodSignature.getMethod();//获取方法
            //判断当前方法是否被RequestMapping注解了 只有注解 才能获取的 方法
            if (method.isAnnotationPresent(RequestMapping.class)) {
                CheckCommit checkCommit = method.getAnnotation(CheckCommit.class);
                long expireTime = checkCommit.expireTime();
                Object[] arg = pjp.getArgs();
                String key = arg[0].toString();
                String value = String.valueOf(System.currentTimeMillis() + expireTime);
                ValueOperations<String, String> ops = redisTemplate.opsForValue();
                Boolean flag = ops.setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
                if (!flag) {
                    log.error("接口重复提交");
                    return null;
                }
                Object[] args = pjp.getArgs();
                obj = pjp.proceed(args);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return obj;
    }

   /**
    * @Description 日志打印
    * @Author Xiaoma
    * @Date 2021/8/13 13:39
    * @param pjp
    * @return java.lang.Object
    **/
    @Around("systemLog()")
    public Object doSystemLogAround(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            Method method = methodSignature.getMethod();//获取方法
            //判断当前方法是否被RequestMapping注解了 只有注解 才能获取的 方法
            if (method.isAnnotationPresent(RequestMapping.class)) {
                SysLog log = new SysLog();
                log.setComeTime(new Date());
                //在通过注解  取出注解内容
                SystemLogger systemLogger = method.getAnnotation(SystemLogger.class);
                log.setType(systemLogger.type());
                RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                String[] value = annotation.value();
                Arrays.stream(value).forEach((s) -> {
                    log.setAction(s);
                });//操作名称
                log.setMethod(method.getName());//方法名称
                sysLogService.save(log);
            }
            //1.获取当前执行方法所需的参数
            Object[] args = pjp.getArgs();
            //2.执行方法并返回
            obj = pjp.proceed(args);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        return obj;
    }
}
